package fr.lelouet.stress;

import java.util.concurrent.Semaphore;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract implementation of the {@link Stressable} interface.<br />
 * To create a instanciable class, implement {@link #calibrate()},
 * {@link #runningLoop()} for the simple stress loop, and
 * {@link #applyConfiguration(Object)} instead of {@link #configure(Object)}
 * 
 * @author guillaume
 * 
 */
public abstract class AStressable implements Stressable {

	private final static Logger logger = LoggerFactory
			.getLogger(AStressable.class);

	/** is this running ? protected with {@link #StateLock} */
	protected boolean running = false;

	/** is this required to stop ? protected with {@link #StateLock} */
	protected boolean hasToStop = false;

	/** the lock on the different elements of the state */
	protected Object StateLock = new Object();

	/**
	 * the call to implement to create a instanciable {@link Stressable}. That
	 * function is looped onto by the run() function.
	 */
	protected abstract void runningLoop();

	@Override
	public void run() {
		synchronized (StateLock) {
			if (running) {
				return;
			}
			hasToStop = false;
			running = true;
		}
		while (!hasToStop) {
			synchronized (StateLock) {
				dirtyConfiguration = false;
				waitingForNewLoop.release(waitingForNewLoop.getQueueLength());
			}
			runningLoop();
		}
		running = false;
	}

	@Override
	public void waitForRun() {
		synchronized (StateLock) {
			while (!running) {
				Thread.yield();
			}
		}
	}

	@Override
	public void stop() {
		synchronized (StateLock) {
			hasToStop = true;
		}
	}

	@Override
	public void stopNow() {
		stop();
		while (running) {
			Thread.yield();
		}
	}

	/** has the configuration been changed since last stress loop ? */
	boolean dirtyConfiguration = false;

	/**
	 * each call to {@link #waitCorrectConfiguration()} try to acquire. It
	 * allows simple synchronization
	 */
	Semaphore waitingForNewLoop = new Semaphore(0);

	@Override
	public boolean configure(Object configuration) {
		boolean res = applyConfiguration(configuration);
		dirtyConfiguration = true;
		return res;
	}

	/** the real usage of the parameter, to implement. */
	public abstract boolean applyConfiguration(Object configuration);

	@Override
	public void waitCorrectConfiguration() {
		if (dirtyConfiguration) {
			try {
				waitingForNewLoop.acquire();
			} catch (Exception e) {
				logger.info("got the following exception : {}; class is {}",
						e.toString(), this.getClass());
				while (dirtyConfiguration) {
					Thread.yield();
				}
			}
		}
	}

}
